﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

using System.Drawing;
using System.Net;
using System.IO;

using System.Windows.Forms;
using mshtml;

namespace HtmlTo.LayoutEngine.MSHtml
{
    public class LayoutDocument : LayoutEngine.LayoutDocument
    {
        #region Constructors/etc

        /// <summary>
        /// <para>Create a document object</para>
        /// </summary>
        public LayoutDocument()
        {
            this.Bounds = new Rectangle(0,0, 800, 600);
        }

        #endregion

        #region Properties

        #endregion

        #region IDocument Members

        private delegate void Add(IHTMLDOMNode node);

        public override void Load(string html)
        {
            WebBrowser webBrowser = new WebBrowser();
            webBrowser.Navigate("about:blank");
            webBrowser.Size = new Size(this.Bounds.Width + 17, this.Bounds.Height);

            // write html to the document
            webBrowser.ScriptErrorsSuppressed = true;
            webBrowser.Document.Write(html);

            HtmlDocument doc = webBrowser.Document;
            HTMLDocumentClass docDom = (HTMLDocumentClass)doc.DomDocument;
            this.Title = doc.Title;

            #region getBounds

            Func<IHTMLElement2, Rectangle> getBounds = element =>
            {
                IHTMLElement2 docElement = (IHTMLElement2)docDom.documentElement;
                IHTMLElement2 bodyElement = (IHTMLElement2)docDom.body;
                IHTMLRect rect = element.getBoundingClientRect();

                return new Rectangle(
                    rect.left + Math.Max(docElement.scrollLeft, bodyElement.scrollLeft) - docElement.clientLeft,
                    rect.top + Math.Max(docElement.scrollTop, bodyElement.scrollTop) - docElement.clientTop,
                    rect.right - rect.left,
                    rect.bottom - rect.top);
            };

            #endregion

            #region getPixel

            Regex pixelRegex = new Regex("^(\\d+)(px)?$", RegexOptions.IgnoreCase);
            Func<IHTMLElement, object, int> getPixelValue = (element, value) =>
            {
                if (value == null) { return 0; }
                string valueString = (string)value;
                Match m = pixelRegex.Match(valueString);
                if (m.Success) { return m.Groups[1].Value.Parse<int>(0); }

                IHTMLElement2 element2 = element as IHTMLElement2;
                string style = (string)element.style.left;
                string runtimeStyle = (string)element2.runtimeStyle.left;

                element2.runtimeStyle.left = element2.currentStyle.left;
                element.style.left = valueString == null ? "0" : valueString;

                int valueInt = element.style.pixelLeft;
                element.style.left = style == null ? "" : style;
                element2.runtimeStyle.left = runtimeStyle == null ? "" : runtimeStyle;

                return valueInt;
            };

            #endregion

            IHTMLDOMNode tempNode = (IHTMLDOMNode)docDom.createElement("_temp");

            Add add = null;
            add = node =>
            {
                LayoutNode layoutNode;

                switch (node.nodeType)
                {
                    case 3:// text node
                        // skip blank text nodes
                        if (node.nodeValue == null
                            || node.nodeValue.ToString().Trim().Length == 0) { return; }

                        LayoutText layoutText = new LayoutText();
                        layoutText.Value = node.nodeValue.ToString();

                        node.replaceNode(tempNode);
                        tempNode.appendChild(node);

                        layoutText.Bounds = getBounds((IHTMLElement2)tempNode);

                        IHTMLElement2 parentElement = (IHTMLElement2)node.parentNode;
                        layoutText.FontFamily = parentElement.currentStyle.fontFamily;
                        layoutText.FontSize = getPixelValue((IHTMLElement)parentElement, parentElement.currentStyle.fontSize);

                        layoutText.IsBold = parentElement.currentStyle.fontWeight.Parse<int>(0) > 600;
                        layoutText.IsItalic = parentElement.currentStyle.fontStyle == "italic";
                        layoutText.IsStrikeout = parentElement.currentStyle.fontStyle == "strike";
                        layoutText.IsUnderline = parentElement.currentStyle.fontStyle == "underline";

                        tempNode.replaceNode(node);

                        layoutNode = layoutText;
                        break;
                    default:
                        LayoutElement layoutElement = new LayoutElement();

                        layoutElement.Bounds = getBounds((IHTMLElement2)node);

                        // add child elements
                        foreach (IHTMLDOMNode childNode in (IEnumerable)node.childNodes)
                        {
                            add(childNode);
                        }

                        layoutNode = layoutElement;
                        break;
                }

                // add layout node to parent
                this.Children.Add(layoutNode);
            };

            // start add
            add((IHTMLDOMNode)webBrowser.Document.Body.DomElement);
        }

        #endregion
    }
}